tree grammar Interpreter;

options {
  language = Java;
  output = AST;
  tokenVocab = TreeConstruction;
  ASTLabelType = InterAST;
}

@header {
    package ru.msu.cmc.sp.oberon2;
    
    import org.antlr.runtime.*;
    import org.antlr.runtime.tree.*;
    import org.antlr.runtime.tree.CommonTree;
    
    import ru.msu.cmc.sp.oberon2.Symbol;
    import ru.msu.cmc.sp.oberon2.SymbolTable;
    import ru.msu.cmc.sp.oberon2.VarEnv;
    import ru.msu.cmc.sp.oberon2.exceptions.RuntimeError;
    import ru.msu.cmc.sp.oberon2.exceptions.RuntimeError.RuntimeErrorType;
    import ru.msu.cmc.sp.oberon2.exceptions.*;
    import java.util.Scanner;
}

@members {
    int EXIT_CODE = 1;
    int CODE = 0;
    Symbol temp,temp1;
    boolean IN_MODULE = false , OUT_MODULE = false, MATH_MODULE = false;
    Symbol increment;
//    public void reportError(RecognitionException e)
//    {
//      if (Oberon2.debug) {
//        System.err.println(getSourceName() + ":" + e.line + ":" + e.charPositionInLine + ":" + e.toString());
//        throw new RuntimeException();
//      }
//      System.exit(100);
//    }
}

program : ^(MODULE include? .* statementSequence[true]?);

include : 'IMPORT' importlist+;

importlist : importmod (COMMA importmod)*;

importmod : a = IDENT {
  if($a.text.equals("In")){
    IN_MODULE = true;
  }
  if($a.text.equals("Out")){
    OUT_MODULE = true;
  }
  if($a.text.equals("Math")){
    MATH_MODULE = true;
  }
//System.out.println($a.text);
};

statementSequence[boolean active]: ^(SEQUENCE (statement[active && CODE == 0])*);

statement[boolean active] :
   assign[active] |
   ifstatement[active]|
   whilestatement[active] |
   forstatement_first[active]|
   loopstatement[active] |
   repeatstatement [active, true]|
   casestatement [active]
 /*
 | procstatement
 */ 
 | call_stmt [active]
 | buildin [active]
 | 'EXIT'{if (active) {CODE = EXIT_CODE;}};

casestatement[boolean active] : ^('CASE' ifstatement[active]);

repeatstatement [boolean active, boolean first] : ^(r = 'REPEAT' e=expr[active] 
  body=statementSequence[first || ((Boolean)Boolean.parseBoolean($e.value.value) && active)]) {
        active = (Boolean)Boolean.parseBoolean($e.value.value) && active;
        TreeNodeStream i = input;
        if (active){
            input = new CommonTreeNodeStream(adaptor, $r.tree);
            repeatstatement(active, false);
    }
    input = i;
}; 

loopstatement [boolean active]: ^(l ='LOOP' statementSequence[active]){
TreeNodeStream input_save = input;
if (active) {
     input = new CommonTreeNodeStream(adaptor, $l.tree);
     loopstatement(active && CODE == 0);
  }
  CODE = 0;
  input = input_save;
};
 
forstatement_first [boolean active] :
^(f='FOR' i=assign[active] to=expr[active] step=expr[active] 
statementSequence[active && 
(Integer.parseInt($i.value.value) < Integer.parseInt($to.value.value))]){
  active = active && (Integer.parseInt($i.value.value) < Integer.parseInt($to.value.value));
  TreeNodeStream input_save = input;
  $i.value.plus($step.value);
  increment = $i.value;
  System.out.println($i.value + " " + increment);
  if (active) {
     input = new CommonTreeNodeStream(adaptor, $f.tree);
     forstatement(active);
  }
  input = input_save;
}; 

forstatement [boolean active] :
^(f='FOR' . to=expr[active] step=expr[active] 
statementSequence[active && 
(Integer.parseInt(increment.value) < Integer.parseInt($to.value.value))]){
  //System.out.println(increment);
  active = active && (Integer.parseInt(increment.value) < Integer.parseInt($to.value.value));
  TreeNodeStream input_save = input;
  if (active) {
     increment.plus($step.value);
     //System.out.println("current incremented variable " + increment.value + " " + increment.name );
     input = new CommonTreeNodeStream(adaptor, $f.tree);
     forstatement(active);
  }
  
  input = input_save;
};

whilestatement [boolean active]: 
    ^(w = 'WHILE' e=expr[active]   body=statementSequence[(Boolean)Boolean.parseBoolean($e.value.value) && active]) {
        //System.out.println((Boolean)Boolean.parseBoolean($e.value.value) && active);
        active = (Boolean)Boolean.parseBoolean($e.value.value) && active;
        TreeNodeStream i = input;
        if (active){
            input = new CommonTreeNodeStream(adaptor, $w.tree);
            whilestatement(active);
    }
    input = i;
}; 
 
ifstatement[boolean active] : 
 ^(IF e=expr[true] statementSequence[(Boolean)Boolean.parseBoolean($e.value.value) && active]
  else_branch[!((Boolean)Boolean.parseBoolean($e.value.value)) && active]){
  }
  
 ;
  
else_branch[boolean active] : ifstatement[active] |  'END' | statementSequence[active];
  
assign [boolean active] returns [Symbol value]: 
    ^(ASSIGN d = designator[active] e = expr[active]) {
    if (active){
        
        $d.env.scope.resolve($d.env.name).assign($e.value);
        $value = $d.env.scope.resolve($d.env.name);
        }
        };
                
expr[boolean active] returns [Symbol value]
@init {
   Double d = Math.random();
   $value = new Symbol(d.toString());
   $value.value = "";
   $value.type = SymbolTable._void;
} :
     ^(EXPR a = expr[active]) {
     if (active){
   $value = $a.value;}
   }
  |   ^(const_fuu a = expr[active]) {
  if (active){
   $value = $a.value;
   }
   }
  |  ^(PLUS a=expr[active] b=expr[active]) {
     if (active){
     $value.assign($a.value);
     $value.plus($b.value);
     }
     }
  | ^(MINUS a=expr[active] b= expr[active]) {
    if (active){
     $value.assign($a.value);
     $value.minus($b.value);
     }
     }
  | ^(STAR a=expr[active] b=expr[active]) {
  if (active){
     $value.assign($a.value);
     $value.mult($b.value);
     }
     }
  | ^(dd='DIV' a=expr[active] b=expr[active]) {
  if (active){
     $value.assign($a.value);
     if ($b.value.value.equals("0")) {
       throw new RuntimeError(dd, RuntimeErrorType.DIVISION_BY_ZERO);
     } else {
       $value.div($b.value);
     }
     }
     }
  | ^('MOD' a=expr[active] b=expr[active]) {
  if (active){
     $value.assign($a.value);
     $value.mod($b.value);
     }
     }
  | ^(OR a=expr[active] b=expr[active]) {
  if (active){
     $value.assign($a.value);
     $value.or($b.value);
     }
     }
  | ^(AND a=expr[active] b=expr[active]) {
  if (active){
     $value.assign($a.value);
     $value.and($b.value);
     }
     }
  | ^(GREATER a=expr[active] b=expr[active]) {
  if (active){
     $value.assign($a.value);
     $value.greater($b.value);
     }
     }
  | ^(GEQ a=expr[active] b=expr[active]) {
  if (active){
     $value.assign($a.value);
     $value.greater_eq($b.value);
     }
     }
  | ^(LESS a=expr[active] b=expr[active]) {
  if (active){
     $value.assign($a.value);
     $value.less($b.value);
     }
     }
  | ^(LEQ a=expr[active] b=expr[active]) {
  if (active){
     $value.assign($a.value);
     $value.less_eq($b.value);
     }
     }
  | ^(EQUAL a=expr[active] b=expr[active]) {
  if (active){
     $value.assign($a.value);
     $value.eq($b.value);
     }
     }
  | INTEGER {
  if (active){
     $value.value = $INTEGER.text;
     $value.type = SymbolTable._int;
     }
     }
  | REAL {
  if (active){
     $value.value = $REAL.text;
     $value.type = SymbolTable._real;
     }
     }
  |  BOOL {
  if (active){
     $value.value = $BOOL.text;
     $value.type = SymbolTable._boolean;
     }
     }
  |  CHAR {
  if (active){
     $value.value = $CHAR.text;
     $value.type = SymbolTable._char;
     }
     }
  |  STRING {
  if (active){
     $value.value = $STRING.text;
     //$value.type = SymbolTable._boolean;
     }
     }
  | v=designator[active] {
  if (active){
   // System.out.println($v.env.scope);
     $value.assign($v.env.scope.resolve($v.env.name));
     }
     };

     
designator[boolean active] returns [VarEnv env] 
@init {
 $env = new VarEnv();
}:
    ^(INDEX qualident expr[active]) {
    if (active){
      $env.name = $qualident.env.name + "[" + $expr.value.value + "]";
      $env.scope = $INDEX.scope;}
      }
   | qualident {
   if(active){
      $env.name = $qualident.env.name;
      $env.scope = $qualident.env.scope;}
      };

qualident returns [VarEnv env]
@init {
 $env = new VarEnv();
}:
    s=(IDENT DOT)? v=IDENT {
      if ($s != null) {
          $env.name = $s.text + $v.text;
      } else {
          $env.name = $v.text;
      }
      $env.scope = $v.scope;
      };
      
const_fuu : CHARCONST |
  STRCONST | 
  BOOLCONST | 
  SHORTCONST| 
  INTCONST |
  LINTCONST |
  REALCONST |
  LREALCONST;

print_elisting[boolean active] returns [Symbol value] : ^(CALLSTMT . ^(ELIST s = expr[active])){
  $value = $s.value;
};

readt_elisting[boolean active] returns [Symbol value] : ^(CALLSTMT . ^(ELIST ^(EXPR v = designator[active]))){
  $value = $v.env.scope.resolve($v.env.name);
};

out_elisting[boolean active] returns [Symbol value, Symbol len] : ^(CALLSTMT . ^(ELIST v = expr[active] l = expr[active])){
  $value = $v.value;
  $len = $l.value;
};


call_stmt[boolean active]:
     ^(e = CALLSTMT s = . .){
    if (active) {
    if ($s.getText().equals("PRINT")){
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        temp = (print_elisting(active)).value;
        System.out.println(temp.value);
        input = save;
    } else if ($s.getText().equals("READ")){
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        temp = (readt_elisting(active)).value;
        Scanner sc = new Scanner(System.in);
        System.out.print(">> ");
        temp.value = sc.next();
        input = save;
    } else if ($s.getText().equals("In.Char")){
    //TODO add check of input
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        temp = (readt_elisting(active)).value;
        Scanner sc = new Scanner(System.in);
        temp.value = sc.next();
        input = save;
    } else if ($s.getText().equals("In.Stirng")){
    //TODO add check of input
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        temp = (readt_elisting(active)).value;
        Scanner sc = new Scanner(System.in);
        temp.value = sc.next();
        input = save;
    } else if ($s.getText().equals("In.Byte")){
    //TODO add check of input
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        temp = (readt_elisting(active)).value;
        Scanner sc = new Scanner(System.in);
        temp.value = sc.next();
        input = save;
    } else if ($s.getText().equals("In.ShotInt")){
    //TODO add check of input
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        temp = (readt_elisting(active)).value;
        Scanner sc = new Scanner(System.in);
        temp.value = sc.next();
        input = save;
    } else if ($s.getText().equals("In.Int")){
    //TODO add check of input
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        temp = (readt_elisting(active)).value;
        Scanner sc = new Scanner(System.in);
        temp.value = sc.next();
        input = save;
    } else if ($s.getText().equals("In.LongInt")){
    //TODO add check of input
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        temp = (readt_elisting(active)).value;
        Scanner sc = new Scanner(System.in);
        temp.value = sc.next();
        input = save;
    } else if ($s.getText().equals("In.Real")){
    //TODO add check of input
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        temp = (readt_elisting(active)).value;
        Scanner sc = new Scanner(System.in);
        temp.value = sc.next();
        input = save;
    } else if ($s.getText().equals("In.LongReal")){
    //TODO add check of input
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        temp = (readt_elisting(active)).value;
        Scanner sc = new Scanner(System.in);
        temp.value = sc.next();
        input = save;
    } else if ($s.getText().equals("Out.Char")){
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        temp = (print_elisting(active)).value;
        System.out.print(temp.value);
        input = save;
    } else if ($s.getText().equals("Out.String")){
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        temp = (print_elisting(active)).value;
        System.out.print(temp.value.substring(1, temp.value.length() - 1));
        input = save;
    } else if ($s.getText().equals("Out.Int")){
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        Interpreter.out_elisting_return temp2 = (out_elisting(active));
        temp = temp2.value;
        Symbol len =  temp2.len;
        System.out.format("\%" + len.value  + "d", Long.parseLong(temp.value));
        input = save;
    } else if ($s.getText().equals("Out.Byte")){
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        Interpreter.out_elisting_return temp2 = (out_elisting(active));
        temp = temp2.value;
        Symbol len =  temp2.len;
        System.out.format("\%" + len.value  + "d", Long.parseLong(temp.value));
        input = save;
    } else if ($s.getText().equals("Out.ShortInt")){
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        Interpreter.out_elisting_return temp2 = (out_elisting(active));
        temp = temp2.value;
        Symbol len =  temp2.len;
        System.out.format("\%" + len.value  + "d", Long.parseLong(temp.value));
        input = save;
    } else if ($s.getText().equals("Out.LongInt")){
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        Interpreter.out_elisting_return temp2 = (out_elisting(active));
        temp = temp2.value;
        Symbol len =  temp2.len;
        System.out.format("\%" + len.value  + "d", Long.parseLong(temp.value));
        input = save;
    }  else if ($s.getText().equals("Out.Real")){
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        Interpreter.out_elisting_return temp2 = (out_elisting(active));
        temp = temp2.value;
        Symbol len =  temp2.len;
        System.out.format("\%" + len.value  + "f", Double.parseDouble(temp.value));
        input = save;
    }  else if ($s.getText().equals("Out.LongReal")){
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        Interpreter.out_elisting_return temp2 = (out_elisting(active));
        temp = temp2.value;
        Symbol len =  temp2.len;
        System.out.format("\%" + len.value  + "f", Double.parseDouble(temp.value));
        input = save;
    }  else if ($s.getText().equals("Out.Ln")){
        TreeNodeStream save = input;
        input = new CommonTreeNodeStream(adaptor, $e.tree);
        System.out.println();
        input = save;
    }
    }
};
      
buildin[boolean active] returns [Symbol value]
@init {
   String res = "";
   Double d = Math.random();
   $value = new Symbol(d.toString());
   $value.value = "";
   $value.type = SymbolTable._void;
}:
^(BUILTINFUNC 'MAX' t = type) {
     if ($t.type.isNumber()) {
       if ($t.type == SymbolTable._shortint) res = "32767";
       if ($t.type == SymbolTable._int) res = "2147483647";
       if ($t.type == SymbolTable._longint) res = "9223372036854775807";
       if ($t.type == SymbolTable._real) res = "3.40282E+38";
       if ($t.type == SymbolTable._longreal) res = "1.79769313486E+308";
       $value.value = res;   
       $value.type = $t.type;                  
  } else {
     res = null;
     $value.value = "";
  }
  }
  | ^(BUILTINFUNC 'MIN' t = type){
     if ($t.type.isNumber()) {
       if ($t.type == SymbolTable._shortint) res = "-32768";
       if ($t.type == SymbolTable._int) res = "-2147483648";
       if ($t.type == SymbolTable._longint) res = "-9223372036854775808";
       if ($t.type == SymbolTable._real) res = "-3.40282E+38";
       if ($t.type == SymbolTable._longreal) res = "-1.79769313486E+308";
       $value.value = res;    
       $value.type = $t.type;                         
  } else {
     res = null;
     $value.value = "";
  }
  }
  | ^(BUILTINFUNC 'SIZE' t = type) {
     if ($t.type.isNumber()) {
       if ($t.type == SymbolTable._boolean) res = "1";
       if ($t.type == SymbolTable._char) res = "8";
       if ($t.type == SymbolTable._shortint) res = "16";
       if ($t.type == SymbolTable._int) res = "32";
       if ($t.type == SymbolTable._longint) res = "64";
       if ($t.type == SymbolTable._real) res = "32";
       if ($t.type == SymbolTable._longreal) res = "64";
       $value.value = res;
       $value.type = SymbolTable._int;                          
  } else {
     res = null;
     $value.value = "";
  }
  }
  | ^(BUILTINFUNC 'VAL' type expr[active]) {if (1==1) throw new RecognitionError($BUILTINFUNC, "Not implemented");}
  | ^(BUILTINFUNC 'ABS' a = expr[active]) {
     if ($a.value.type.isNumber()) {
       res = $a.value.value;
       if (res.charAt(0) == '-') res = res.substring(1);
       $value.value = res; 
       $value.type = $a.value.type;                      
  } else {
     res = null;
     $value.value = "";
  }
  }
  | ^(BUILTINFUNC 'ASH' a = expr[active] b = expr[active]) {
     if ($a.value.type.isInt() && $b.value.type.isInt()) {
       long x  = Long.parseLong($a.value.value);
       long n  = Long.parseLong($b.value.value);
       res = String.valueOf(x << n);
       $value.value = res;
       $value.type = SymbolTable._longint;                      
  } else {
     res = null;
     $value.value = "";
  }
  }
  | ^(BUILTINFUNC 'CHR' expr[active])  {if (1==1) throw new RecognitionError($BUILTINFUNC, "Not implemented");}
  | ^(BUILTINFUNC 'ENTIER' expr[active])  {if (1==1) throw new RecognitionError($BUILTINFUNC, "Not implemented");}
  | ^(BUILTINFUNC 'EVEN' expr[active])  {if (1==1) throw new RecognitionError($BUILTINFUNC, "Not implemented");}
  | ^(BUILTINFUNC 'LEN' a = expr[active]) {
     if ($a.value.type instanceof ArrayType && ((ArrayType) $a.value.type).elementType == SymbolTable._char) {
       res = String.valueOf(($a.value.value.length() - 2));
       $value.value = res;
       $value.type = SymbolTable._longint;   
     } else {
       res = null;
       $value.value = "";
     }
  }
  | ^(BUILTINFUNC 'LSH' expr[active] expr[active]) {if (1==1) throw new RecognitionError($BUILTINFUNC, "Not implemented");}
  | ^(BUILTINFUNC 'ODD' expr[active])  {if (1==1) throw new RecognitionError($BUILTINFUNC, "Not implemented");}
  | ^(BUILTINFUNC 'ORD' expr[active]) {if (1==1) throw new RecognitionError($BUILTINFUNC, "Not implemented");};
/*

repeatStatement : ^('REPEAT' e=. s=.)
{
    int next = input.index();
    Symbol condition;
    input.seek($s.getTokenStartIndex());
    do {
       sequence();
       input.seek($e.getTokenStartIndex());
       condition = expr().value;
    } while (Boolean.parseBoolean(condition.value));
};

 };*/
 
 type returns [Type type]
  : ^(ARRAY (n = inumber)? typeElement) //warning, but it works!
    {
        int num;
        if ($n.start == null) num = -1;
        else num = Integer.parseInt($n.value);
        $type = new ArrayType($typeElement.type, num);
     }
  | typeElement {$type = $typeElement.type;}
  ;
  
  
typeElement returns [Type type]
@init {InterAST t = (InterAST)input.LT(1);}
    :   'REAL' {$type = SymbolTable._real;}
    |   'BYTE' {$type = SymbolTable._byte;}
    |   'INTEGER' {$type = SymbolTable._int;}
    |   'LONGINT' {$type = SymbolTable._longint;}
    |   'SHORTINT' {$type = SymbolTable._shortint;}
    |   'LONGREAL' {$type = SymbolTable._longreal;}
    |   'CHAR' {$type = SymbolTable._char;}
    |   'BOOLEAN' {$type = SymbolTable._boolean;}
    |   IDENT {
        t.symbol = $IDENT.scope.resolve(t.getText());
        t.scope = $IDENT.scope;
        $type = (Type)t.symbol;
        if ($type == null) 
        {throw new RecognitionError(0,0, "Unknown type " + t.getText());}
    }
    ;
    
inumber returns [String value, Type type]
    : ^(INTCONST INTEGER)   { $value = $INTEGER.text; $type = SymbolTable._int;} 
    | ^(SHORTCONST INTEGER) { $value = $INTEGER.text; $type = SymbolTable._shortint;}   
    | ^(LINTCONST INTEGER)  { $value = $INTEGER.text; $type = SymbolTable._longint;}  
    ;  